package com.spring;

import com.spring.annotation.*;
import com.spring.core.BeanDefinition;
import com.spring.core.BeanPostProcessor;
import com.spring.core.InitializingBean;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author lh
 * @Date 2021-09-29 19:16
 */
public class ConsumerApplication {

    private Class configClass;

    /**
     * 根据beanName存放Bean描述信息
     */
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    /**
     * 根据bean类型存bean描述信息
     */
    private Map<Class, BeanDefinition> typeBeanDefinitionMap = new HashMap<>();
    /**
     * 单例池
     */
    private Map<String, Object> singletonObjects = new HashMap<>();
    /**
     * 类型池  方便依赖注入时根据类型注入
     */
    private Map<Class, Object> typeSingletonObjects = new HashMap<>();

    /**
     * 存放所有的beanPostProcessor
     */
    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();

    public ConsumerApplication(Class configClass) {
        this.configClass = configClass;
        //扫描类
        scan(configClass);
        //创建bean
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            //判断是否是单例bean  并且不是懒加载的加入单例池
            if ("singleton".equals(beanDefinitionEntry.getValue().getScope()) && !beanDefinitionEntry.getValue().getIsLazy()) {
                Object singletonBean = createBean(beanDefinitionEntry.getValue());
                //存入单例池
                singletonObjects.put(beanDefinitionEntry.getKey(), singletonBean);
                //存入类型单例池方便依赖注入时根据类型注入
                typeSingletonObjects.put(beanDefinitionEntry.getValue().getClassType(), singletonBean);
            }
        }
    }

    public Object getBean(String beanName) {
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new NullPointerException();
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //是否是懒加载
        if (beanDefinition.getIsLazy() && beanDefinition.getScope().equals("singleton")) {
            if (singletonObjects.containsKey(beanName)) {
                return singletonObjects.get(beanName);
            } else {
                Object bean = createBean(beanDefinition);
                //存入单例池
                singletonObjects.put(beanDefinition.getBeanName(), bean);
                //存入类型单例池方便依赖注入时根据类型注入
                typeSingletonObjects.put(beanDefinition.getClassType(), bean);
                return bean;
            }
        }
        if (beanDefinition.getScope().equals("singleton")) {
            return singletonObjects.get(beanName);
        } else {
            return createBean(beanDefinition);
        }
    }

    /**
     * 这里会有循环依赖问题
     */
    private Object createBean(BeanDefinition value) {
        Object instance = null;
        try {
            Constructor constructor = value.getClassType().getConstructor();
            constructor.setAccessible(true);
            instance = constructor.newInstance();
            //依赖注入
            fieldAssignment(instance);
            //执行beanPostProcessor的前置方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                beanPostProcessor.postProcessBeforeInitialization(instance, value.getBeanName());
            }
            //执行初始化方法
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }
            //执行beanPostProcessor的后置方法  Spring中AOP在这里完成
            for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                beanPostProcessor.postProcessAfterInitialization(instance, value.getBeanName());
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return instance;
    }

    private void fieldAssignment(Object instance) throws IllegalAccessException {
        //获取对象中的所有字段
        Field[] fields = instance.getClass().getDeclaredFields();
        for (Field field : fields) {
            Autowired autowiredAnnotation = field.getAnnotation(Autowired.class);
            //对属性上Autowired注解的字段依赖注入
            if (autowiredAnnotation != null) {
                field.setAccessible(true);
                Class type = field.getType();
                Set<Class> classkeys = typeBeanDefinitionMap.keySet();
                //获取字段类型   根据字段类型获取池中他的子类或者实现他的接口
                List<Class> classLists = classkeys.stream().filter((k) -> type.isAssignableFrom(k)).collect(Collectors.toList());
                if (classLists.size() > 0) {
                    //有多个可以注入的对象
                    if (classLists.size() > 1) {
                        //如果有多个根据名字去找
                        String name = field.getName();
                        if (singletonObjects.containsKey(name)) {
                            singletonObjects.get(name);
                        } else {
                            List<String> beanNames = new ArrayList<>();
                            for (Class c : classLists) {
                                beanNames.add(typeBeanDefinitionMap.get(c).getBeanName());
                            }
                            throw new RuntimeException("没有找到name为" + name + "的bean，只有" + beanNames);
                        }
                        System.out.println(name);
                    } else {
                        //只有一个可以注入的对象
                        //判断对象是否已经创建，有时候可能出现还没有开始实例化到这个bean的情况
                        if (typeSingletonObjects.containsKey(classLists.get(0))) {
                            //赋值给属性
                            field.set(instance, typeSingletonObjects.get(classLists.get(0)));
                        } else {
                            //根据属性的class获取到BeanDefinition
                            BeanDefinition beanDefinition = typeBeanDefinitionMap.get(classLists.get(0));
                            Object bean = createBean(beanDefinition);
                            //存入类型池
                            typeSingletonObjects.put(classLists.get(0), bean);
                            //存入单例池
                            singletonObjects.put(beanDefinition.getBeanName(), bean);
                            field.set(instance, bean);
                        }
                    }
                } else {
                    //容器中没有找到可以注入的对象
                    throw new RuntimeException("没有找到可以注入的对象");
                }
            }
        }
    }

    private void scan(Class configClass) {
        ComponentScan scanAnnotation = (ComponentScan) this.configClass.getAnnotation(ComponentScan.class);
        if (scanAnnotation != null) {
            try {
                String value = scanAnnotation.value().replace(".", "/");
                URL resource = configClass.getClassLoader().getResource(value);
                File file = new File(resource.getPath());
                if (file.isDirectory()) {
                    List<File> AllFileList = getAllFiles(file, new ArrayList<>());
                    for (File f : AllFileList) {
                        String absolutePath = f.getAbsolutePath();
                        System.out.println(absolutePath);
                        String classPath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class")).replace("\\", ".");
                        Class aClass = configClass.getClassLoader().loadClass(classPath);
                        Component componentAnnotation = (Component) aClass.getAnnotation(Component.class);
                        if (componentAnnotation != null) {
                            //判断扫描类是否实现了BeanPostProcessor接口
                            if (BeanPostProcessor.class.isAssignableFrom(aClass)) {
                                beanPostProcessors.add((BeanPostProcessor) aClass.getConstructor().newInstance());
                            } else {
                                BeanDefinition beanDefinition = new BeanDefinition();
                                String beanName = componentAnnotation.value();
                                //判断Component注解中没定义名字的话给他一个默认名字
                                if (Objects.equals("", beanName)) {
                                    beanName = Introspector.decapitalize(aClass.getSimpleName());
                                }
                                beanDefinition.setClassType(aClass);
                                beanDefinition.setBeanName(beanName);
                                Scope scopeAnnotation = (Scope) aClass.getAnnotation(Scope.class);
                                //判断是否是单例bean
                                if (scopeAnnotation == null) {
                                    beanDefinition.setScope("singleton");
                                } else {
                                    beanDefinition.setScope(scopeAnnotation.value());
                                }
                                Lazy lazyAnnotation = (Lazy) aClass.getAnnotation(Lazy.class);
                                if (lazyAnnotation == null) {
                                    beanDefinition.setIsLazy(false);
                                } else {
                                    beanDefinition.setIsLazy(true);
                                }
                                beanDefinitionMap.put(beanName, beanDefinition);
                                typeBeanDefinitionMap.put(aClass, beanDefinition);
                            }
                        }
                    }
                }
            } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    public List<File> getAllFiles(File directory, List<File> list) {
        for (File file : directory.listFiles()) {
            if (file.isDirectory()) {
                getAllFiles(file, list);
            } else {
                list.add(file);
            }
        }
        return list;
    }
}